#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include <readline/readline.h>
#include <readline/history.h>

#include "ctree.h"
#include "builtins.h"
#include "hash.h"

/*
 * run - eval a command
 *
 * @argc command arguments count
 * @argv command arguments array
 *
 * return -1 on error, or command return value
 */
static int run(int argc, char **argv)
{
  ctree_node_t cmd;

  /* command alias has been handled in parse phase */
  
  /* then check if it's a registered command */
  cmd = ctree_get_node(current, argv[0]);
  if (cmd != NULL) {
    if (cmd->type == CTREE_TYPE_NLEAF) {
      current = cmd;
      return 0;
    } else {
      return cmd->action(argc, argv);
    }
  }

  /*
   * Well, check builtin command at last.
   * Help me, Obi-Wan Kenobi; you're my only hope.
   */
  return cli_builtins_run(argc, argv);
}

/*
 * get_token - get a token from the string
 *
 * @str pointer to the string pointer to be analyzed, value-result argument
 * @tok pointer to the string pointer to store the token, value-result argument
 *
 * return: 
 *    -1  syntax error
 *     0  no token exist
 *     1  found 1 token
 */
static int get_token(char **str, char **tok)
{
  int inquote = 0;
  char *s = *str;
  char *t = *tok;

  /* strip leading blanks */
  for (; isspace(*s); s++);

  if (*s == 0) {
    return 0;
  }

  while (*s != 0) {
    if (*s == '\\') {
      s++;
      
      if (*s == 0) {
        return -1;
      }
      
      *t++ = *s;
    } else {
      if (inquote) {
        if (*s == inquote) {
          inquote = 0;
        } else {
          *t++ = *s;
        }
      } else {
        if (*s == '"' || *s == '\'') {
          inquote = *s;
        } else if (isspace(*s)) {
          break;
        } else {
          *t++ = *s;
        }
      }
    }

    s++;
  }

  if (inquote) {
    return -1;
  }

  *t++ = 0;
  *tok = t;
  *str = s;
  
  return 1;
}

/*
 * dealias - de-alias an command string
 *
 * @str the command string to be dealiased
 *
 * return %NULL on error, or the dealiased command string pointer, this pointer
 * should be freed by caller later.
 */
static char *dealias(char *str)
{
  int rv;
  char *first_alias;
  char *s, *ptr, *t;

  if (!str) {
    return NULL;
  } else {
    s = str = strdup(str);
    if (!str) {
      perror("strdup");
      return NULL;
    }
  }
  
  ptr = strdup(str);
  if (!ptr) {
    perror("strdup");
    free(str);
    return NULL;
  }

  t = ptr;

  rv = get_token(&s, &t);
  if (rv != 1) { /* syntax error or no token exist, just let them go... */
    /*
     * Note:
     * 1. rv==0 is not an error condition, so we can't just return NULL;
     * 2. contents ptr pointed to may be changed by get_token(), so we can
     * not depend on it.
     */
    free(ptr);
    return str;
  }

  first_alias = strdup(ptr);
  if (!first_alias) {
    perror("strdup");
    free(str);
    free(ptr);
    return NULL;
  }

  /* now de-aliase the string */
  while(1) {
    hash_t *hi = hash_table_index(alias_table, ptr);

    if (hi) { /* yes, we found an alias */
      free(ptr);
      ptr = malloc(strlen(((alias_t*)hi)->alias) + strlen(s) + 1);
      if (!ptr) {
	perror("malloc");
	free(str);
	free(first_alias);
	return NULL;
      }

      /* de-alias */
      *ptr = 0;
      strcat(ptr, ((alias_t*)hi)->alias);
      strcat(ptr, s);

      free(str);
      str = strdup(ptr);
      if (!str) {
	perror("strdup");
	free(ptr);
	free(first_alias);
	return NULL;
      }

      s = str;
      t = ptr;
      rv = get_token(&s, &t);
      /*
       * Similar code segment with few lines before in this function, so I
       * won't bother to comment it again. Errr... well, this is another kind
       * of comments indeed...
       */	 
      if (rv != 1) {
	free(ptr);
	free(first_alias);
	return str;
      }

      /* sanity check: is this an recursive alias? */
      if (strcmp(first_alias, ptr) == 0) {
	fprintf(stderr, "error: %s: recursive alias.\n", first_alias);
	free(str);
	free(ptr);
	free(first_alias);
	return NULL;
      }
    } else { /* no, it's not an alias */
      free(ptr);
      free(first_alias);
      return str;
    }
  }

  return NULL;
}

/*
 * parse - parse and run command string
 *
 * @str the command to be parsed
 *
 * return -1 on error, or command return value
 */
static int parse(char *str)
{
  int i, rv;
  int argc = 0;
  char **argv = NULL;
  char *s, *ptr, *t;

  /* first we de-alias the string */
  str = dealias(str);
  if (!str) {
    return -1;
  }
  s   = str;
  ptr = strdup(str);
  if (!ptr) {
    perror("strdup");
    free(str);
    return -1;
  }
  t   = ptr;

  /* now tokenize the string to get argc and argv */
  while (1) {
    rv = get_token(&s, &t);

    if (rv == -1) {
      fprintf(stderr, "syntax error.\n");
      free(str);
      free(ptr);
      return -1;
    } else if (rv == 0) {
      break;
    } else if (rv == 1) {
      argc++;
    }
  }

  if (argc == 0) {
    free(str);
    free(ptr);
    return 0;
  }

  argv = malloc(sizeof(char*) * argc);

  argv[0] = ptr;
  for (i=1; i<argc; i++) {
    argv[i] = argv[i-1] + strlen(argv[i-1] + 1);
  }

  /* run the command */
  rv = run(argc, argv);

  free(str);
  free(ptr);
  
  return rv;
}

/*
 * cli_mainloop - the main CLI read-eval loop
 */
static int cli_mainloop(void)
{
  char *cmdline;
  
  while(current) {
    /* display prompt to user */
    current->prompt();

    /* get user input */
    cmdline = readline(NULL);

    /* parse and exec */
    parse(cmdline);

    free(cmdline);
  }

  return 0;
}

/*
 * cli - libcli entrance
 *
 * return -1 on error, 0 on success
 */

int cli(void)
{
  if (cli_builtins_init() < 0) {
    return -1;
  }
  
  cli_mainloop();

  cli_builtins_fini();

  return 0;
}
